Mapper Container topic
MapperContainer
s are the backbone of dart_mappable
. A MapperContainer
s job is to lookup the correct
mapper for a type and call its respective method.
To find the mapper for a given type, the container first looks at its own set of mappers and when there is no match it refers to its linked containers.
🚨 Important!
A lot of this may be removed in a future version. See https://github.com/schultek/dart_mappable/issues/159 for more details.
Working with MapperContainer
s
This is an advanced topic. Usually you don't need to worry about containers, as they are hidden from the user when doing standard
things like decoding or encoding from json or using the generated mixins toString
, ==
or hashCode
overrides.
You don't need to know this for most use-cases. But feel free to read on if you are interested.
By default there is only the globally accessible MapperContainer.globals
. The interface of a container contains all the respective methods for
serialization and mapping, like fromJson
, toJson
, isEqual
, hash
and asString
. You can use these methods with any object as long
as the container can resolve a mapper for it.
To control which types a MapperContainer
can resolve see the following methods:
-
use<T>(MapperBase<T> mapper)
will add a new mapper for typeT
. -
unuse<T>()
will remove the current mapper for typeT
. Note that this works for any mapper, custom, generated or for primitive types. -
useAll(Iterable<MapperBase> mapper)
works asuse()
but for a list of mappers. -
get<T>()
will return the current mapper for typeT
. -
getAll()
will return all currently registered mappers.
The MapperContainer.global
has a default set of mappers for all core Dart types. When calling MyClassMapper.ensureInitialized()
on a generated mapper, it will add itself to the global container and from thereon be usable through it.
Additionally to using the preconfigured containers, you can also create one yourself using the MapperContainer()
constructor.
In extension to controlling mappers manually for a single container, you can also link a container to another. That way container A can use all mappers of container B implicitly.
link(MapperContainer container)
links the provided container to the current one.
The following example shows this in action for a schematic setup of two classes and mappers.
// Suppose we have two classes and their respective mappers
class A {}
class B {}
class AMapper extends MapperBase<A> {} // generated
class BMapper extends MapperBase<B> {} // generated
void main() {
AMapper.ensureInitialized();
// succeeds - AMapper was added to the globals container.
MapperContainer.globals.toJson(A());
// fails - B is not initialized yet.
MapperContainer.globals.toJson(A());
BMapper.ensureInitialized();
// creates a new empty container
var containerX = MapperContainer();
// fails - no mappers were added yet
containerX.toJson(A());
// succeeds - added the respective mapper
containerX.use(AMapper());
containerX.toJson(A());
var containerY = MapperContainer();
containerY.use(BMapper());
// succeeds - added the respective mapper
containerY.toJson(B());
// fails - missing mapper for type A
containerY.toJson(A());
// links containerX to containerY
containerY.link(containerX);
// succeeds - mapper is resolved through linked containerX
containerY.toJson(A());
// fails - linking is one-directional
containerX.toJson(B());
}
Defaults Container
There is a special global defaults container that can be accessed using MapperContainer.defaults
. This
container initially contains mappers for all primitive and core types. Types included are:
dynamic
, Object
, String
, int
, double
, num
, bool
, DateTime
, List
, Set
, Map
All other containers are implicitly linked to this default container.
You can use this container to add mappers for types, that are thereby supported by all other containers.
// suppose we have a class and a custom mapper for it
class MyClass {}
class MyClassMapper extends SimpleMapper<MyClass> { ... }
void main() {
var container = MapperContainer();
// this doen't work, since the mapper for type [MyClass] wasn't added yet to this container
container.toJson(MyClass());
// we add the mapper as a global default
MapperContainer.defaults.use(MyClassMapper());
// now this works, since all containers are linked to the default container
container.toJson(MyClass());
}
Globals Container
The second special container is the MapperContainer.globals
container. This
container is used by all generated mappers for global registration.
A generated mapper adds itself to the globals container when calling MyMapper.ensureInitialized()
.
Encoding Lists, Sets and Maps
Because all of the MapperContainer
s methods are generic, you are not limited to use a single
object with these methods. dart_mappable
also support List
s, Set
s and Map
s out of the box,
without any special syntax, workarounds or hacks.
@MappableClass()
class Dog with DogMappable {
String name;
Dog(this.name);
}
@MappableClass()
class Box<T> with BoxMappable<T> {
T content;
Box(this.content);
}
void main() {
// Case 1: Simple list.
// We use the default container since we only use core types.
List<int> nums = MapperContainer.globals.fromJson('[2, 4, 105]');
print(nums); // [2, 4, 105]
// Case 2: Set of objects.
// We use the generated container for [Dog], but with a set of objects.
DogMapper.ensureInitialized();
Set<Dog> dogs = MapperContainer.globals.fromJson('[{"name": "Thor"}, {"name": "Lasse"}, {"name": "Thor"}]');
print(dogs); // {Dog(name: Thor), Dog(name: Lasse)}
// Case 3: More complex lists, like generics.
// We use the generated container for [Box], but with a list of generic objects.
BoxMapper.ensureInitialized();
List<Box<double>> boxes = MapperContainer.globals.fromJson('[{"content": 0.1}, {"content": 12.34}]');
print(boxes); // [Box(content: 0.1), Box(content: 12.34)]
}
There is also the MapperContainer.fromIterable
method. This can be used if you already have a list of dynamic objects instead of the raw json string.
Additionally this can get handy to decode a dynamic list of partly-encoded values:
List<double> myNumbers = MapperContainer.globals.fromIterable([2.312, '1.32', 500, '1e4']);
print(myNumbers); // [2.312, 1.32, 500.0, 10000.0]
Non-Trivial Maps
We also support decoding of non-trivial maps. Although the use-cases might be rare, you can decode to something other than a map of string keys like this:
var encodedMap = {
{'name': 'Bonny'}: 1,
{'name': 'Clyde'}: 5,
};
DogMapper.ensureInitialized();
Map<Dog, int> treatsPerDog = MapperContainer.globals.fromValue(encodedMap);
print(treatsPerDog[Dog('Clyde')]!); // 5
var myMap = MapperContainer.globals.toValue(treatsPerDog);
print(myMap); // {{name: Bonny}: 1, {name: Clyde}: 5}
Note: Make sure to add @MappableClass
on your key class, in order to enable easy property access.
Note: Since json only supports string keys, we can't do fromJson
or toJson
on these maps.
You would have to decode / encode your keys and values separately.
🎉 Congrats, you followed the tour until the end. Now you know everything about this package.
Classes
- DecodingOptions Generics Mapper Container
- Additional options to be passed to MapperContainer.fromValue.
- EncodingOptions Generics Mapper Container
- Additional options to be passed to MapperContainer.toValue.
- MapperContainer Generics Mapper Container
- The mapper container manages a set of mappers and is the main interface for accessing mapping functionalities.
Exceptions / Errors
- MapperException Mapper Container
- General exception class used throughout the package.